package com.example.goband.game;

import com.example.goband.Config.WebSocketConfig;
import com.example.goband.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 22479
 * Date: 2023-06-02
 * Time: 16:14
 */

//这个类表示匹配器,这个类负责完成匹配功能
@Component
public class Matcher {

    @Autowired
    private OnlineUserManager onlineUserManager;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private RoomManager roomManager;

    private Queue<User> normalQueue = new LinkedList<>();
    private Queue<User> highQueue = new LinkedList<>();
    private Queue<User> veryHighQueue = new LinkedList<>();

    //玩家点击开始匹配
    public void add(User user) {
        if(user.getScore() < 2000) {
            //多线程下进行加锁,保证线程安全
            synchronized(normalQueue) {
                normalQueue.add(user);
                normalQueue.notify();
            }
            System.out.println("[Matcher] " + user.getUsername() + " 进入 normalQueue!");
        } else if(user.getScore() >= 2000 && user.getScore() < 3000) {
            synchronized (highQueue) {
                highQueue.add(user);
                highQueue.notify();
            }
            System.out.println("[Matcher] " + user.getUsername() + " 进入 highQueue!");
        } else {
            synchronized (veryHighQueue) {
                veryHighQueue.add(user);
                veryHighQueue.notify();
            }
            System.out.println("[Matcher] " + user.getUsername() + " 进入 veryHighQueue!");
        }
    }

    //玩家点击停止匹配
    public void remove(User user) {
        if (user.getScore() < 2000) {
            //多线程下进行加锁,保证线程安全
            synchronized(normalQueue) {
                removeFromQueue(normalQueue, user);
            }
            System.out.println("[Matcher] " + user.getUserId() + " 移出 normalQueue!");
        } else if (user.getScore() >= 2000 && user.getScore() < 3000) {
            synchronized (highQueue) {
                removeFromQueue(highQueue, user);
            }
            System.out.println("[Matcher] " + user.getUserId() + " 移出 highQueue!");
        } else {
            synchronized (veryHighQueue) {
                removeFromQueue(veryHighQueue, user);
            }
            System.out.println("[Matcher] " + user.getUserId() + " 移出 veryHighQueue!");
        }
    }

    private void removeFromQueue(Queue<User> queue, User user) {
        queue.remove(user);
    }

    public Matcher() {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                //扫描normalQueue
                while(true) {
                    handlerMatch(normalQueue);
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                //扫描highQueue
                while(true) {
                    handlerMatch(highQueue);
                }
            }
        };
        t2.start();

        Thread t3 = new Thread() {
            @Override
            public void run() {
                //扫描veryHighQueue
                while(true) {
                    handlerMatch(veryHighQueue);
                }
            }
        };
        t3.start();

    }

    //匹配处理器
    public void handlerMatch(Queue<User> queue) {
        //多线程下进行加锁,保证线程安全
        synchronized (queue) {
            try {
                // 1. 检测队列中元素个数是否达到 2
                //    队列的初始情况可能是 空
                //    如果往队列中添加一个元素, 这个时候, 仍然是不能进行后续匹配操作的.
                //    因此在这里使用 while 循环检查是更合理的
                while(queue.size() < 2) {
                    queue.wait();
                }

                //2、尝试从队列中取出两个玩家
                User player1 = queue.poll();
                User player2 = queue.poll();
                System.out.println("匹配出两个玩家: " + player1.getUsername() + ", " + player2.getUsername());

                //3、获取到玩家的websocket的会话
                //    获取到会话的目的是为了告诉玩家, 你排到了
                WebSocketSession session1 = onlineUserManager.getFromGameHall(player1.getUserId());
                WebSocketSession session2 = onlineUserManager.getFromGameHall(player2.getUserId());

                // 理论上来说, 匹配队列中的玩家一定是在线的状态.
                // 因为前面的逻辑里进行了处理, 当玩家断开连接的时候就把玩家从匹配队列中移除了.
                // 但是此处仍然进行一次判定
                if(session1 == null) {
                    // 如果玩家1 现在不在线了, 就把玩家2 重新放回到匹配队列中
                    queue.offer(player1);
                    return;
                }
                if(session2 == null) {
                    queue.offer(player2);
                    return;
                }

                // 当前能否排到两个玩家是同一个用户的情况嘛? 一个玩家入队列了两次?
                // 理论上也不会存在
                // 1) 如果玩家下线, 就会对玩家移出匹配队列
                // 2) 又禁止了玩家多开.
                // 但是仍然在这里多进行一次判定, 以免前面的逻辑出现 bug 时带来严重的后果.
                if (session1 == session2) {
                    // 把其中的一个玩家放回匹配队列.
                    queue.offer(player1);
                    return;
                }

                // 4. 把这两个玩家放到一个游戏房间中.
                // todo ...
                Room room = new Room();
                // todo..user加入Room放到GameAPI连接成功后 也就是真正打开游戏房间页面建立websocket连接后
                roomManager.add(room, player1.getUserId(), player2.getUserId());


                //5、给玩家反馈信息 ：你匹配到对手了
                //    通过 websocket 返回一个 message 为 'matchSuccess' 这样的响应  | 约定好的
                //    此处是要给两个玩家都返回 "匹配成功" 这样的信息.
                //    因此就需要返回两次
                MatchResponse response1 = new MatchResponse();
                response1.setMessage("matchSuccess");
                response1.setOk(true);
                session1.sendMessage(new TextMessage(objectMapper.writeValueAsString(response1)));

                MatchResponse response2 = new MatchResponse();
                response2.setMessage("matchSuccess");
                response2.setOk(true);
                session2.sendMessage(new TextMessage(objectMapper.writeValueAsString(response2)));

            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }

}
